use std::{
    fmt::{Display, Formatter},
    fs::File,
    io::Read,
    str::FromStr,
    sync::{Arc, Mutex},
    thread,
};

use rand::Rng;
use serde::Deserialize;
use serde_json;

const CPU_THREADS: usize = 16;
const MAX_CHARACTER_NUM: u8 = 6;
const MAX_WEAPON_NUM: u8 = 5;

#[derive(Deserialize, Debug)]
struct PoolDataJson {
    probability:    Vec<f32>
}

#[derive(Deserialize, Debug)]
struct DiceDataJson {
    newbie:     PoolDataJson,
    weapon:     PoolDataJson,
    character1: PoolDataJson,
    character2: PoolDataJson,
    regular:    PoolDataJson,
}
impl Into<ProbabilityData> for DiceDataJson {
    fn into(self) -> ProbabilityData {
        ProbabilityData {
            newbie:         ProbabilityRow::from(self.newbie.probability),
            weapon:         ProbabilityRow::from(self.weapon.probability),
            character1:     ProbabilityRow::from(self.character1.probability),
            character2:     ProbabilityRow::from(self.character2.probability),
            regular:        ProbabilityRow::from(self.regular.probability),
        }
    }
}
#[derive(Clone)]
struct ProbabilityRow(Vec<f32>);
impl ProbabilityRow {
    fn from(vec: Vec<f32>) -> Self {
        Self(vec)
    }
    fn dice(&self, index: usize) -> bool {
        if index >= self.0.len() {
            false
        } else {
            let mut rng = rand::thread_rng();
            let rand_num: f32 = rng.gen();
            rand_num < self.0[index]
        }
    }
}
struct ProbabilityData {
    newbie:         ProbabilityRow,
    weapon:         ProbabilityRow,
    character1:     ProbabilityRow,
    character2:     ProbabilityRow,
    regular:        ProbabilityRow,
}

#[derive(Clone, Debug)]
enum DiceChoice {
    WEAPON, CHARACTER
}
impl Display for DiceChoice {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let ret = match self {
            DiceChoice::WEAPON => {"weapon"}
            DiceChoice::CHARACTER => {"character"}
        };
        write!(f, "{ret}")
    }
}
type DicePath = Vec<DiceChoice>;
struct DicePaths(Vec<DicePath>);
impl DicePaths {
    fn new(vec: Vec<DicePath>) -> Self {
        DicePaths(vec)
    }
    fn from(current_owned_num: (u8, u8)) -> Self {

        let c_aim = MAX_CHARACTER_NUM - current_owned_num.0;
        let w_aim = MAX_WEAPON_NUM - current_owned_num.1;

        let len = c_aim + w_aim;
        let mut ret = vec![];

        for i in 0..1<<len {
            let mut path = vec![];
            for j in 0..len {
                path.push(if (i >> j) & 1 == 1 {
                    DiceChoice::WEAPON
                } else {
                    DiceChoice::CHARACTER
                })
            }
            ret.push(path);
        }
        DicePaths(ret)
    }
    fn paths(&self) -> &Vec<DicePath> {
        &self.0
    }

    fn at(&self, index: usize) -> &DicePath {
        assert!(index < self.0.len());
        &self.0[index]
    }
}

struct Simulator {
    dice_num:           u16,
    simulate_rounds:    usize,
    owned_num:          (u8, u8), // c, w
    diced_num:          (u8, u8), // c, w
    enhancement:        (Vec<f32>, Vec<f32>),
    pdf:                (ProbabilityRow, ProbabilityRow) //c, w
}

impl Simulator {
    pub fn new(
        dice_num: u16, simulate_rounds: usize, owned_num: (u8, u8), diced_num: (u8, u8),
        enhancement: (Vec<f32>, Vec<f32>), pdf: (ProbabilityRow, ProbabilityRow)
    ) ->  Self {
        Self {
            dice_num,
            simulate_rounds,
            owned_num,
            diced_num,
            enhancement,
            pdf
        }
    }

    pub fn run(&self, result: Arc<Mutex<(DicePath, f32)>>) -> () {
        let all_path = DicePaths::from(self.owned_num);
        self.run_by_given_paths(all_path, result)
    }


    fn run_by_given_paths(&self, paths: DicePaths, result: Arc<Mutex<(DicePath, f32)>>) -> () {
        let mut best_result: (DicePath, f32) = (vec![], 0f32); // path, value

        let all_path_len = paths.paths().len();
        for index in 0..all_path_len {
            let path = paths.at(index);
            let mut sum = 0f32;
            println!("{index}/{all_path_len}");

            for _ in 0..self.simulate_rounds {
                let mut path_index = 0;
                let mut dice_index = self.diced_num;
                let mut result = (0u8, 0u8); // c, w

                for i in 0..self.dice_num {
                    if path_index >= path.len() { break }
                    let pool = &path[path_index];

                    match pool {
                        DiceChoice::CHARACTER => {
                            if self.pdf.0.dice(dice_index.0 as usize) {
                                dice_index.0 = 0;
                                path_index += 1;
                                result.0 += 1;
                                continue
                            }
                            dice_index.0 += 1;
                        }
                        DiceChoice::WEAPON => {
                            if self.pdf.1.dice(dice_index.1 as usize) {
                                dice_index.1 = 0;
                                path_index += 1;
                                result.1 += 1;
                                continue
                            }
                            dice_index.1 += 1;
                        }
                    }
                }
                let value = self.value(result);
                sum += value;
            }

            if sum / self.simulate_rounds as f32 > best_result.1 {
                best_result.0 = (*path).clone();
                best_result.1 = sum / self.simulate_rounds as f32;
            }
        }
        let mut all_result = result.lock().unwrap();

        if best_result.1 > all_result.1 {
            all_result.1 = best_result.1;
            all_result.0 = best_result.0.clone();
        }

    }

    fn value(&self, result: (u8, u8)) -> f32 {
        let total = (result.0 + self.owned_num.0, result.1 + self.owned_num.1); // c, w
        let len = (self.enhancement.0.len() as u8, self.enhancement.1.len() as u8); // c, w
        let total = (
            if total.0 > len.0 { len.0 } else { total.0 } as usize,
            if total.1 > len.1 { len.1 } else { total.1 } as usize
        ); // c, w

        let enhance: (&[f32], &[f32]) = (
            &self.enhancement.0[self.owned_num.0 as usize..total.0],
            &self.enhancement.1[self.owned_num.1 as usize..total.1]
        );

        let mut v = 1f32;
        for item in enhance.0 { v *= item }
        for item in enhance.1 { v *= item }

        v
    }
}
impl Clone for Simulator {
    fn clone(&self) -> Self {
        Simulator {
            dice_num: self.dice_num,
            simulate_rounds: self.simulate_rounds,
            owned_num: self.owned_num.clone(),
            diced_num: self.diced_num.clone(),
            enhancement: (self.enhancement.0.clone(), self.enhancement.1.clone()),
            pdf: (self.pdf.0.clone(), self.pdf.0.clone()),
        }
    }
}
fn read_json(path: &str) -> ProbabilityData {
    let mut dir = std::env::current_dir().unwrap();
    dir.push(path);
    println!("{:?}", dir);
    let mut f = File::open(dir).unwrap();
    let mut data_str = String::new();
    f.read_to_string(&mut data_str).unwrap();

    serde_json::from_str::<DiceDataJson>(data_str.as_ref()).unwrap().into()
}


fn main() {
    let p_data = read_json("res\\pdf_dice_data.json");
    let start = chrono::Local::now().timestamp();

    // ---------------------------------CONFIG---------------------------------
    let owned_num = (0, 0);
    let diced_num = (0, 0);
    let enhancement
        = (vec![0.4, 0.2, 0.1, 0.2, 0.5, 0.8],
           vec![0.5, 0.3, 0.3, 0.3, 0.2]);
    let pdf = (p_data.character1, p_data.weapon);
    // ---------------------------------CONFIG---------------------------------

    let simulator
        = Simulator::new(
        100,
        1_000_000,
        owned_num,
        diced_num,
        enhancement,
        pdf);

    let mut result = Arc::new(Mutex::new((vec![], 0f32)));

    let mut handles = vec![];
    let all_path = DicePaths::from(simulator.owned_num);
    let chunks = all_path.0.chunks(all_path.0.len() / CPU_THREADS);

    for paths in chunks {
        let result = Arc::clone(&result);
        let paths = paths.to_vec();
        let simulator = simulator.clone();
        let handle = thread::spawn(move || {
            let paths = DicePaths::new(paths);
            simulator.run_by_given_paths(paths, result);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap()
    }
    let end = chrono::Local::now().timestamp();
    println!("{:?}", result.lock().unwrap());
    println!("Total Time Consuming: {}s.", end - start);
}

